/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.zentello.sbot;

import com.zentello.sbot.Configuration;
import java.util.Map;
import groovy.lang.GroovyClassLoader;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import com.google.gson.Gson;
import com.zentello.sbot.Test;
import com.zentello.sbot.TestSuiteObserver;
import java.io.FileReader;
import java.util.Arrays;
import static com.zentello.util.Log.*;

/**
 *
 * @author anthony
 */
public class TestSuite extends HashMap {
    ArrayList<Test> tests = new ArrayList<Test>();
    ArrayList<TestSuiteObserver> observers = new ArrayList<TestSuiteObserver>();
    FileWriter logWriter = null;
    private boolean go;
    private Configuration config;
    private String homeDir = "";
    public void addTest(Test test){
        tests.add(test);
        test.setSuite(this);
    }
    
    public void addObserver(TestSuiteObserver observer){
        if(observer==null){
            warn("Attempt to add null observer");
            return;
        }
        info("Added observer " + observer.getClass().getName());
        observers.add(observer);
    }
    
    public void logInfo(String message){
        log("INFO",message);
    }
    
    void log(String prefix, String message){
        String output = (prefix!=null?(prefix + ":"):("")) + message + "\r\n";
        Iterator<TestSuiteObserver> oit = observers.iterator();
        while(oit.hasNext()){
            oit.next().log(output);
        }
    }
    
    public void setUp(){
        loadConfiguration();
        if(this.containsKey("_LOGLEVEL")){
            setLogLevel((String)get("_LOGLEVEL"));
        }
        prepareLoader();
        loadObservers();
        loadTests();
    }
    
    boolean overall;   
    public boolean run(){
        overall=true;
        go = true;
                
        SimpleDateFormat df = new SimpleDateFormat("yyyy.MM.dd:HH.mm.ss.SS");
        GregorianCalendar greg = new GregorianCalendar();
        greg.setTimeInMillis((Long)get("_LASTMODIFIED"));
        log("\r\nSTART",df.format(new GregorianCalendar().getTime()));
        log("CONFIG", get("_PATH") + " " + df.format(greg.getTime()));
        Iterator<Test> it = tests.iterator();
        Test test;
        String name;
        boolean setupOK=false, runOK=false, teardownOK=false; 
        while(it.hasNext() && go){
            setupOK=false; runOK=false; teardownOK=false;
            test = it.next();
            name = test.getClass().getSimpleName();
            logInfo("Running " + name);
            try{
                test.setup();
                setupOK = true;
            }
            catch(AssertionError ae){
                log("ASSERTERR",ae.getMessage());
            }
            catch(Exception e){
                log("CRITICAL",e.getMessage());                
            }
            try{
                test.runTest();
                runOK = true;
            }
            catch(AssertionError ae){
                log("ASSERTERR",ae.getMessage());
            }
            catch(Exception e){
                log("CRITICAL",e.getMessage());                
            }
            try{
                test.teardown();
                teardownOK = true;
            }
            catch(AssertionError ae){
                log("ASSERTERR",ae.getMessage());
            }
            catch(Exception e){
                log("CRITICAL",e.getMessage());                
            }
            // Notify observers of the results of the test
            Iterator<TestSuiteObserver> oit = observers.iterator();
            while(oit.hasNext()){
                oit.next().testFinished(name, setupOK && runOK && teardownOK);
            }
            if(!(setupOK && runOK && teardownOK)){
                overall = false;

                if(!setupOK){
                    oit = observers.iterator();
                    while(oit.hasNext()){
                        oit.next().setupFailed(name);
                    }
                    log("FAILED", "SETUP:" + name);
                    //System.out.println("Failed:Setup:" + name);
                }
                if(!runOK){
                    oit = observers.iterator();
                    while(oit.hasNext()){
                        oit.next().testFailed(name);
                    }
                    log("FAILED", "TEST:" + name);
                    //System.out.println("Failed:Test:" + name);
                }
                if(!teardownOK){
                    oit = observers.iterator();
                    while(oit.hasNext()){
                        oit.next().teardownFailed(name);
                    }
                    log("FAILED", "TEARDOWN:" + name);
                    //System.out.println("Failed:Teardown:" + name);
                }
            }
        }
        log(overall?"PASS":"FAILED","OVERALL");
        log("END",df.format(new GregorianCalendar().getTime()));
        // Notify observers that suite is suiteFinished
        Iterator<TestSuiteObserver> oit = observers.iterator();
        TestSuiteObserver observer;
        while(oit.hasNext()){
            observer = oit.next();
            observer.suiteFinished(overall);
            observer.teardown();
        }
        return overall;
    }
    GroovyClassLoader loader;
    
    private void prepareLoader(){
        loader = new GroovyClassLoader();
    	// Load classpath
        if(config.getGlobalParameters().containsKey("_CLASSPATH")){
            String classpath = (String) config.getGlobalParameters().get("_CLASSPATH");
            Iterator<String> it = (Arrays.asList(classpath.split(","))).iterator();
            while(it.hasNext()){
                    loader.addClasspath(homeDir + it.next().trim());
            }
        }
    }
    
    private void loadTests() {
    	//Load tests        
    	if(config.getTests()!=null){
            Map<String,Map<String,String>> map = config.getTests();
            String path, name;
            Test test;
            Iterator<String> it = map.keySet().iterator();
            while(it.hasNext()){
                name = it.next();
                path = homeDir + name;
                try {
                    test = (Test)loader.parseClass(new File(path)).newInstance();
                    test.setStringParameters(map.get(name));
                    addTest(test);
                } catch (Exception e) {
                    log("ERR","Skipping test " + path + " : " + e.toString());
                    err("Skipping test " + path + " : " + e.toString());
                    overall=false;
                }
            }    	
	}
        else{
            log("WARN", "No tests found in configuration!");
        }
    }

    private void loadObservers() {
    	//Load observers
    	if(config.getObservers()!=null){
            Map<String,Map<String,String>> map = config.getObservers();
            String path, name;
            TestSuiteObserver observer;
            Iterator<String> it = map.keySet().iterator();
            while(it.hasNext()){
                name = it.next();
                path = homeDir + name;
                try {
                    observer = (TestSuiteObserver)loader.parseClass(new File(path)).newInstance();
                    observer.setStringParameters(map.get(name));
                    addObserver(observer);
                } catch (Exception ex) {
                    log("ERR","Skipping observer " + path + " : " + ex.toString());
                    err(ex.toString());
                    overall=false;
                }
            }
        }
    }

    void loadConfiguration() {
        String path = (String)get("_CONFIGPATH");
        File file = new File(path);
        //log("D",file.getAbsolutePath() + " " + file.exists());
        if(file.exists()){
            Gson gson = new Gson();
            try {

                Configuration config = gson.fromJson(new FileReader(path), Configuration.class);
                this.putAll(config.getGlobalParameters());
                this.config = config;
            } catch (Exception ex) {
                log("ERR", ex.getMessage());
                err(ex.getMessage());
                overall=false;
            }
            put("_LASTMODIFIED",file.lastModified());
            put("_PATH", path);
            // Set home directory
            if(containsKey("_HOMEDIR")){
                homeDir = (String)get("_HOMEDIR");
                info("Setting _HOMEDIR to " + homeDir);
            }
            else{
                info("_HOMEDIR not defined. Set to default " + new File(homeDir).getAbsolutePath());
                this.put("_HOMEDIR", homeDir);
            }
        }
        else{
            log("WARN", "No configuration file found @ " + path);
            warn("No configuration file found @ " + path);
        }
    }

    int getTestCount() {
        return tests.size();
    }

    void stop() {
        go = false;
    }

}
